-- Communicate
-- Create by weism
-- 通讯模块

module("Communicate", package.seeall);

require("core/module/Socket");
require("game/logic/module/HeartBeatM");
require("game.logic.module.ScheduleM");

local socket = require "socket";

-- 读取数据报的状态
local WAIT_SYNC1  = 0;
local WAIT_SYNC2  = 1;
local WAIT_HEADER = 2;
local WAIT_BODY   = 3;
local readState = WAIT_SYNC1;

-- 接收到的二进制包(尚没有解析)
local data = Buffer.create(0);

-- 包头信息
local pktHeader = {
    cmdNo = 0,
    HEADER_SIZE = 16,
};

local schedulerId = nil;

-- 调试开关
local debugPrintMsg = false;

-- 内部函数声明
local processMsg;

-- 模块初始化处理流程
function init()
    cclog("载入通讯文件描述");
    MSG.init();
    PktAnalyser.loadCommDesc();

    -- 定时循环处理通讯
    if not isVerifyClient() then
        schedulerId = cc.Director:getInstance():getScheduler():scheduleScriptFunc(update, 0.03, false);
    end
end

function reset()
    if schedulerId ~=nil then
        cc.Director:getInstance():getScheduler():unscheduleScriptEntry(schedulerId);
        schedulerId = nil;
    end
end

-- 开启垃圾回收功能
local function enableGC()
    collectgarbage("collect");
    collectgarbage("setpause", 100);
    collectgarbage("setstepmul", 5000);
end

-- 禁止垃圾回收功能
local function disableGC()
    --collectgarbage("stop");
end

-- 网络相关的定时处理
function update()
    -- socket的处理
    Socket.update();

    -- 处理消息
    MSG.update();

    -- 心跳
    HeartBeatM.update();

    -- 定时器
    ScheduleM.update();
end

-- 发送消息
function send(cmd, lpc)
    local id = iif(type(cmd) == "string", MSG.getMsgId(cmd), cmd);

    if not USE_CPP_PACKER then
        -- 打包消息体
        local body = PktAnalyser.pack(id, lpc);

        -- 打包消息头
        local buffer = Buffer.create(0);
        Buffer.set8(buffer, 1, 77);                      -- M
        Buffer.set8(buffer, 2, 90);                      -- Z
        Buffer.setu16(buffer, 3, 0);                     -- checksum
        Buffer.setu32(buffer, 5, 0);                     -- tickcount
        Buffer.setu32(buffer, 9, 2 + Buffer.size(body)); -- 消息长度
        Buffer.set16(buffer, 13, 0);                     -- 对齐位
        Buffer.setu16(buffer, 15, id);                   -- 消息编号

        -- 附加消息体
        buffer = Buffer.append(buffer, body);

        -- 发送消息
        Socket.send(buffer);
    else
        -- 打包消息
        local buffer = pack_message(id, lpc);

        -- 发送消息
        Socket.send(buffer);
    end

    EventMgr.fire(event.SEND_MESSAGE, {["msg_no"] = id, });
end

-- 发送二进制消息（已经打包完毕了）
function sendRaw(cmd, body)
    local id = iif(type(cmd) == "string", MSG.getMsgId(cmd), cmd);

    local bodyLen = 0;
    if USE_CPP_PACKER then
        bodyLen = #body;
    else
        bodyLen = Buffer.size(body);
    end

    -- 打包消息头
    local buffer = Buffer.create(0);
    Buffer.set8(buffer, 1, 77);                      -- M
    Buffer.set8(buffer, 2, 90);                      -- Z
    Buffer.setu16(buffer, 3, 0);                     -- checksum
    Buffer.setu32(buffer, 5, 0);                     -- tickcount
    Buffer.setu32(buffer, 9, 2 + bodyLen);           -- 消息长度
    Buffer.set16(buffer, 13, 0);                     -- 对齐位
    Buffer.setu16(buffer, 15, id);                   -- 消息编号

    -- 附加消息体
    buffer = Buffer.append(buffer, body);

    if USE_CPP_PACKER then
        buffer = buffer.value;
    end

    -- 发送消息
    Socket.send(buffer);
end

-- 允许/禁止打印消息
function debugOn()  debugPrintMsg = true  end
function debugOff() debugPrintMsg = false end

-- 读取消息(newData为新读取的内容)
function read(newData)

    if USE_CPP_PACKER then
        socket_receive_data(newData);
    else
        data = Buffer.append(data, newData);

        -- 开始解析所有的消息
        local v;
        local offset = 1;
        while true do
            -- 没有任何数据，不再读取
            if Buffer.size(data) - offset + 1 < 1 then break end

            -- 根据状态进行解析消息包
            if readState == WAIT_SYNC1 then
                -- 等待第一个标记：M
                v = Buffer.get8(data, offset);
                offset = offset + 1;
                if v == 77 then readState = WAIT_SYNC2 end
            elseif readState == WAIT_SYNC2 then
                -- 等待第二个标记：Z
                v = Buffer.get8(data, offset);
                offset = offset + 1;
                readState = iif(v == 90, WAIT_HEADER, WAIT_SYNC1);
            elseif readState == WAIT_HEADER then
                -- 数据不够就等待
                if Buffer.size(data) - offset + 1 < pktHeader.HEADER_SIZE - 2 then break end

                -- 读取头部信息
                offset = offset + 2; -- checksum
                offset = offset + 4; -- tickcount
                pktHeader.msgLen = Buffer.get32(data, offset) - 2;
                offset = offset + 4;
                offset = offset + 2; -- 对齐位
                pktHeader.cmdNo = Buffer.getu16(data, offset);
                offset = offset + 2;
                pktHeader.time = socket.gettime();

                if pktHeader.msgLen > 0 then
                    -- 切换到等待数据包
                    readState = WAIT_BODY;
                else
                    -- 没有数据包了，直接处理掉
                    processMsg(pktHeader.cmdNo, Buffer.create(0));
                    readState = WAIT_SYNC1;
                end
            else
                -- 数据不够就等待
                if Buffer.size(data) - offset + 1 < pktHeader.msgLen then break end

                -- 读取消息体
                local body = Buffer.getstr(data, offset, pktHeader.msgLen);
                offset = offset + pktHeader.msgLen;

                -- 解析并处理这个消息
                local b = Buffer.create(0);
                local logMsg = string.format("processMsg cmdNo:0x%04X, len:%d", pktHeader.cmdNo, #body);
                if pktHeader.cmdNo ~= 0xFF01 and debugPrintMsg then
                    cclog(logMsg);
                end
                Buffer.setstr(b, 1, #body, body);
                Profiler.addSocketRead(pktHeader.cmdNo, socket.gettime() - pktHeader.time);
                local now = socket.gettime();
                processMsg(pktHeader.cmdNo, b);
                Profiler.addUnpack(pktHeader.cmdNo, socket.gettime() - now);

                -- 切换到继续读取下一条消息的状态
                readState = WAIT_SYNC1;
            end
        end

        -- 已经解析过的废除掉
        data = Buffer.sub(data, offset, -1);
    end
end

-- 解析并分发处理一条消息
processMsg = function(cmdNo, body)
    -- 如果是心跳信息，直接处理掉
    if cmdNo == 0xff01 then
        sendRaw(0xff02, body);
        return;
    end

    -- 解包
    local v = PktAnalyser.unpack(cmdNo, body);
    if not v then
        cclog("解包失败了。");
        return false;
    else
        -- 仍到队列中
        MSG.addMsg(cmdNo, v);
        return true;
    end
end
